﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.Xml;
using System.Data;
using System.Xml.Serialization;

namespace BinaryStructProject
{
    // TODO: XmlElement
    // TODO: XmlDocument
    // TODO: Рефакторинг
    /*
     * Отдельные классы writer, reader, surrogateSelector, surrogates
     */
    // TODO: Упаковка значений типов занимающие много байт маркирующим байтом
    // TODO: Упаковка булевых значений в вектора
    // TODO: флаги null тоже паковать в булевые вектора

    /*
     * Проверять если тип сереализируемый, то сериализовать с помощью BinaryFormatter,
     * иначе с помощью XmlSerializer.
     */

    /*
enum LongPack : byte
{
    Long1byte,
    Long2byte,
    Long3byte,
    ...
} 
     */

    [Serializable]
    public class OneBigType : IEquatable<OneBigType>
    {
        public char mChar;
        public byte mByte;
        public short mShort;
        public int mInt;
        public double mDbl;
        public DateTime mDt;
        public Guid mGuid;
        public String mString;
        public byte[] mBytes;          // A byte array member
        public DataPoint mDp;          // A value type member
        //public OneBigType mSelf;       // A reference type member
        public TimeSpan mTs;
        public SampleEnum mEnum;
        public List<SampleInfo> mList;
        public List<long> mLongList;
        public List<string> mStringList;
        public string[] mStringAr;
        public SampleInfo[] mInfoAr;
        public object mObj;
        public object[] mObjAr;
        public List<object> mObjList;
        public DataTable mDataTable;
        public XmlElement mXmlElement;

        public override bool Equals(object obj)
        {
            return Equals(obj as OneBigType);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public bool Equals(OneBigType other)
        {
            if (other == null)
                return false;
            return this.mChar == other.mChar && this.mByte == other.mByte && this.mShort == other.mShort &&
                this.mInt == other.mInt && this.mDbl == other.mDbl && this.mDt == other.mDt &&
                this.mGuid == other.mGuid && this.mString == other.mString && this.mDp.Equals(other.mDp) &&
                IsArraysEquals<Byte>(this.mBytes, other.mBytes, (x, y) => x == y) &&
                this.mTs == other.mTs && this.mEnum == other.mEnum &&
                IsListsEquals(this.mList, other.mList, (x, y) => SampleInfo.Equals(x, y)) &&
                IsListsEquals(this.mLongList, other.mLongList, (x, y) => x == y) &&
                IsListsEquals(this.mStringList, other.mStringList, (x, y) => string.Equals(x, y)) &&
                IsArraysEquals<string>(this.mStringAr, other.mStringAr, (x, y) => string.Equals(x, y)) &&
                IsArraysEquals<SampleInfo>(this.mInfoAr, other.mInfoAr, (x, y) => SampleInfo.Equals(x, y)) &&
                IsObjectsEquals(this.mObj, other.mObj) &&
                IsArraysEquals(this.mObjAr, other.mObjAr, (x, y) => IsObjectsEquals(x, y)) &&
                IsListsEquals(this.mObjList, other.mObjList, (x, y) => IsObjectsEquals(x, y)) &&
                IsDataTablesEquals(this.mDataTable, other.mDataTable) &&
                IsXmlElementsEquals(this.mXmlElement, other.mXmlElement);
        }

        private bool IsXmlElementsEquals(XmlNode el1, XmlNode el2)
        {
            if (object.ReferenceEquals(el1, el2))
                return true;
            if (el1 == null || el2 == null)
                return false;
            if (!(el1.Name == el2.Name && el1.NamespaceURI == el2.NamespaceURI && el1.Prefix == el2.Prefix &&
                el1.Value == el2.Value))
                return false;
            if (el1.Attributes.Count != el2.Attributes.Count)
                return false;
            for (int i = 0; i < el1.Attributes.Count; i++)
            {
                if (!IsXmlAttrsEquals(el1.Attributes[i], el2.Attributes[i]))
                    return false;
            }
            if (el1.ChildNodes.Count != el2.ChildNodes.Count)
                return false;
            for (int i = 0; i < el1.ChildNodes.Count; i++)
            {
                if (!IsXmlElementsEquals(el1.ChildNodes[i], el2.ChildNodes[i]))
                    return false;
            }
            return true;
        }

        private bool IsXmlAttrsEquals(XmlAttribute atr1, XmlAttribute atr2)
        {
            if (object.ReferenceEquals(atr1, atr2))
                return true;
            if (atr1 == null || atr2 == null)
                return false;
            return atr1.Name == atr2.Name && atr1.NamespaceURI == atr2.NamespaceURI && atr1.Prefix == atr2.Prefix &&
                atr1.Value == atr2.Value;
        }

        private bool IsDataTablesEquals(DataTable dt1, DataTable dt2)
        {
            if (object.ReferenceEquals(dt1, dt2))
                return true;
            if (dt1 == null || dt2 == null)
                return false;
            if (!string.Equals(dt1.TableName, dt2.TableName))
                return false;
            if (dt1.Columns.Count != dt2.Columns.Count)
                return false;
            for (int i = 0; i < dt1.Columns.Count; i++)
            {
                if (!string.Equals(dt1.Columns[i].ColumnName, dt2.Columns[i].ColumnName) ||
                    dt1.Columns[i].DataType != dt2.Columns[i].DataType)
                    return false;
            }
            if (dt1.Rows.Count != dt2.Rows.Count)
                return false;
            for (int i = 0; i < dt1.Rows.Count; i++)
            {
                for (int j = 0; j < dt1.Columns.Count; j++)
                {
                    if (dt1.Rows[i].IsNull(j) != dt2.Rows[i].IsNull(j))
                        return false;
                    if (!dt1.Rows[i].IsNull(j))
                    {
                        IComparable comp = dt1.Rows[i][j] as IComparable;
                        if (comp == null)
                            return false;
                        if (comp.CompareTo(dt2.Rows[i][j]) != 0)
                            return false;
                    }
                }
            }
            return true;
        }

        private bool IsObjectsEquals(object obj1, object obj2)
        {
            if (object.ReferenceEquals(obj1, obj2))
                return true;
            if (obj1 == null || obj2 == null)
                return false;
            IComparable comp = obj1 as IComparable;
            if (comp != null)
                return comp.CompareTo(obj2) == 0;
            return obj1.Equals(obj2);
        }

        private bool IsArraysEquals<T>(T[] ar1, T[] ar2, Func<T, T, bool> equal)
        {
            if (Object.ReferenceEquals(ar1, ar2))
                return true;
            if (ar1 == null || ar2 == null)
                return false;
            if (ar1.Length != ar2.Length)
                return false;
            for (int i = 0; i < ar1.Length; i++)
            {
                if (!equal(ar1[i], ar2[i]))
                    return false;
            }
            return true;
        }

        private bool IsListsEquals<T>(List<T> list1, List<T> list2, Func<T, T, bool> equal)
        {
            if (object.ReferenceEquals(list1, list2))
                return true;
            if (list1 == null || list2 == null)
                return false;
            if (list1.Count != list2.Count)
                return false;
            for (int i = 0; i < list1.Count; i++)
            {
                if (!equal(list1[i], list2[i]))
                    return false;
            }
            return true;
        }
    }

    [StructLayout(LayoutKind.Sequential), Serializable()]
    public struct DataPoint : IEquatable<DataPoint>
    {
        public double mdX;
        public double mdY;
        public double mdZ;
        public short msX;
        public short msY;
        public short msZ;
        public float mfX;
        public float mfY;
        public float mfZ;

        public override bool Equals(object obj)
        {
            return Equals((DataPoint)obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public bool Equals(DataPoint other)
        {
            return this.mdX == other.mdX && this.mdY == other.mdY && this.mdZ == other.mdZ && this.msX == other.msX &&
                this.msY == other.msY && this.msZ == other.msZ && this.mfX == other.mfX && this.mfY == other.mfY &&
                this.mfZ == other.mfZ;
        }
    }

    public enum SampleEnum
    {
        One,
        Two,
        Three
    }

    [Serializable]
    public class SampleInfo
    {
        public string Name;
        public decimal Value;

        public static bool Equals(SampleInfo sInfo1, SampleInfo sInfo2)
        {
            if (object.ReferenceEquals(sInfo1, sInfo2))
                return true;
            if (sInfo1 == null || sInfo2 == null)
                return false;
            return string.Equals(sInfo1.Name, sInfo2.Name) && sInfo1.Value == sInfo2.Value;
        }
    }
}
